/**
 * jQuery Masonry v2.1.05
 * A dynamic layout plugin for jQuery
 * The flip-side of CSS Floats
 * http://masonry.desandro.com
 *
 * Licensed under the MIT license.
 * Copyright 2012 David DeSandro
 */

/*jshint browser: true, curly: true, eqeqeq: true, forin: false, immed: false, newcap: true, noempty: true, strict: true, undef: true */
/*global jQuery: false */

(function(window, $, undefined) {

	'use strict';

	/*
	 * smartresize: debounced resize event for jQuery
	 *
	 * latest version and complete README available on Github:
	 * https://github.com/louisremi/jquery.smartresize.js
	 *
	 * Copyright 2011 @louis_remi
	 * Licensed under the MIT license.
	 */

	var $event = $.event, resizeTimeout;

	$event.special.smartresize = {
		setup : function() {
			$(this).bind("resize", $event.special.smartresize.handler);
		},
		teardown : function() {
			$(this).unbind("resize", $event.special.smartresize.handler);
		},
		handler : function(event, execAsap) {
			// Save the context
			var context = this, args = arguments;

			// set correct event type
			event.type = "smartresize";

			if (resizeTimeout) {
				clearTimeout(resizeTimeout);
			}
			resizeTimeout = setTimeout(function() {
				$.event.handle.apply(context, args);
			}, execAsap === "execAsap" ? 0 : 100);
		}
	};

	$.fn.smartresize = function(fn) {
		return fn ? this.bind("smartresize", fn) : this.trigger("smartresize",
				[ "execAsap" ]);
	};

	// ========================= Masonry ===============================

	// our "Widget" object constructor
	$.Mason = function(options, element) {
		this.element = $(element);

		this._create(options);
		this._init();
	};

	$.Mason.settings = {
		isResizable : true,
		isAnimated : false,
		animationOptions : {
			queue : false,
			duration : 500
		},
		gutterWidth : 0,
		isRTL : false,
		isFitWidth : false,
		containerStyle : {
			position : 'relative'
		}
	};

	$.Mason.prototype = {

		_filterFindBricks : function($elems) {
			var selector = this.options.itemSelector;
			// if there is a selector
			// filter/find appropriate item elements
			return !selector ? $elems : $elems.filter(selector).add(
					$elems.find(selector));
		},

		_getBricks : function($elems) {
			var $bricks = this._filterFindBricks($elems).css({
				position : 'absolute'
			}).addClass('masonry-brick');
			return $bricks;
		},

		// sets up widget
		_create : function(options) {

			this.options = $.extend(true, {}, $.Mason.settings, options);
			this.styleQueue = [];

			// get original styles in case we re-apply them in .destroy()
			var elemStyle = this.element[0].style;
			this.originalStyle = {
				// get height
				height : elemStyle.height || ''
			};
			// get other styles that will be overwritten
			var containerStyle = this.options.containerStyle;
			for ( var prop in containerStyle) {
				this.originalStyle[prop] = elemStyle[prop] || '';
			}

			this.element.css(containerStyle);

			this.horizontalDirection = this.options.isRTL ? 'right' : 'left';

			this.offset = {
				x : parseInt(this.element.css('padding-'
						+ this.horizontalDirection), 10),
				y : parseInt(this.element.css('padding-top'), 10)
			};

			this.isFluid = this.options.columnWidth
					&& typeof this.options.columnWidth === 'function';

			// add masonry class first time around
			var instance = this;
			setTimeout(function() {
				instance.element.addClass('masonry');
			}, 0);

			// bind resize method
			if (this.options.isResizable) {
				$(window).bind('smartresize.masonry', function() {
					instance.resize();
				});
			}

			// need to get bricks
			this.reloadItems();

		},

		// _init fires when instance is first created
		// and when instance is triggered again -> $el.masonry();
		_init : function(callback) {
			this._getColumns();
			this._reLayout(callback);
		},

		option : function(key, value) {
			// set options AFTER initialization:
			// signature: $('#foo').bar({ cool:false });
			if ($.isPlainObject(key)) {
				this.options = $.extend(true, this.options, key);
			}
		},

		// ====================== General Layout ======================

		// used on collection of atoms (should be filtered, and sorted before )
		// accepts atoms-to-be-laid-out to start with
		layout : function($bricks, callback) {

			// place each brick
			for ( var i = 0, len = $bricks.length; i < len; i++) {
				this._placeBrick($bricks[i]);
			}

			// set the size of the container
			var containerSize = {};
			containerSize.height = Math.max.apply(Math, this.colYs);
			if (this.options.isFitWidth) {
				var unusedCols = 0;
				i = this.cols;
				// count unused columns
				while (--i) {
					if (this.colYs[i] !== 0) {
						break;
					}
					unusedCols++;
				}
				// fit container to columns that have been used;
				containerSize.width = (this.cols - unusedCols)
						* this.columnWidth - this.options.gutterWidth;
			}
			this.styleQueue.push({
				$el : this.element,
				style : containerSize
			});

			// are we animating the layout arrangement?
			// use plugin-ish syntax for css or animate
			var styleFn = !this.isLaidOut ? 'css'
					: (this.options.isAnimated ? 'animate' : 'css'), animOpts = this.options.animationOptions;

			// process styleQueue
			var obj;
			for (i = 0, len = this.styleQueue.length; i < len; i++) {
				obj = this.styleQueue[i];
				obj.$el[styleFn](obj.style, animOpts);
			}

			// clear out queue for next time
			this.styleQueue = [];

			// provide $elems as context for the callback
			if (callback) {
				callback.call($bricks);
			}

			this.isLaidOut = true;
		},

		// calculates number of columns
		// i.e. this.columnWidth = 200
		_getColumns : function() {
			var container = this.options.isFitWidth ? this.element.parent()
					: this.element, containerWidth = container.width();

			// use fluid columnWidth function if there
			this.columnWidth = this.isFluid ? this.options
					.columnWidth(containerWidth) :
			// if not, how about the explicitly set option?
			this.options.columnWidth ||
			// or use the size of the first item
			this.$bricks.outerWidth(true) ||
			// if there's no items, use size of container
			containerWidth;

			this.columnWidth += this.options.gutterWidth;

			this.cols = Math.floor((containerWidth + this.options.gutterWidth)
					/ this.columnWidth);
			this.cols = Math.max(this.cols, 1);

		},

		// layout logic
		_placeBrick : function(brick) {
			var $brick = $(brick), colSpan, groupCount, groupY, groupColY, j;

			//how many columns does this brick span
			colSpan = Math.ceil($brick.outerWidth(true) / this.columnWidth);
			colSpan = Math.min(colSpan, this.cols);

			if (colSpan === 1) {
				// if brick spans only one column, just like singleMode
				groupY = this.colYs;
			} else {
				// brick spans more than one column
				// how many different places could this brick fit horizontally
				groupCount = this.cols + 1 - colSpan;
				groupY = [];

				// for each group potential horizontal position
				for (j = 0; j < groupCount; j++) {
					// make an array of colY values for that one group
					groupColY = this.colYs.slice(j, j + colSpan);
					// and get the max value of the array
					groupY[j] = Math.max.apply(Math, groupColY);
				}

			}

			// get the minimum Y value from the columns
			var minimumY = Math.min.apply(Math, groupY), shortCol = 0;

			// Find index of short column, the first from the left
			for ( var i = 0, len = groupY.length; i < len; i++) {
				if (groupY[i] === minimumY) {
					shortCol = i;
					break;
				}
			}

			// position the brick
			var position = {
				top : minimumY + this.offset.y
			};
			// position.left or position.right
			position[this.horizontalDirection] = this.columnWidth * shortCol
					+ this.offset.x;
			this.styleQueue.push({
				$el : $brick,
				style : position
			});

			// apply setHeight to necessary columns
			var setHeight = minimumY + $brick.outerHeight(true), setSpan = this.cols
					+ 1 - len;
			for (i = 0; i < setSpan; i++) {
				this.colYs[shortCol + i] = setHeight;
			}

		},

		resize : function() {
			var prevColCount = this.cols;
			// get updated colCount
			this._getColumns();
			if (this.isFluid || this.cols !== prevColCount) {
				// if column count has changed, trigger new layout
				this._reLayout();
			}
		},

		_reLayout : function(callback) {
			// reset columns
			var i = this.cols;
			this.colYs = [];
			while (i--) {
				this.colYs.push(0);
			}
			// apply layout logic to all bricks
			this.layout(this.$bricks, callback);
		},

		// ====================== Convenience methods ======================

		// goes through all children again and gets bricks in proper order
		reloadItems : function() {
			this.$bricks = this._getBricks(this.element.children());
		},

		reload : function(callback) {
			this.reloadItems();
			this._init(callback);
		},

		// convienence method for working with Infinite Scroll
		appended : function($content, isAnimatedFromBottom, callback) {
			if (isAnimatedFromBottom) {
				// set new stuff to the bottom
				this._filterFindBricks($content).css({
					top : this.element.height()
				});
				var instance = this;
				setTimeout(function() {
					instance._appended($content, callback);
				}, 1);
			} else {
				this._appended($content, callback);
			}
		},

		_appended : function($content, callback) {
			var $newBricks = this._getBricks($content);
			// add new bricks to brick pool
			this.$bricks = this.$bricks.add($newBricks);
			this.layout($newBricks, callback);
		},

		// removes elements from Masonry widget
		remove : function($content) {
			this.$bricks = this.$bricks.not($content);
			$content.remove();
		},

		// destroys widget, returns elements and container back (close) to original style
		destroy : function() {

			this.$bricks.removeClass('masonry-brick').each(function() {
				this.style.position = '';
				this.style.top = '';
				this.style.left = '';
			});

			// re-apply saved container styles
			var elemStyle = this.element[0].style;
			for ( var prop in this.originalStyle) {
				elemStyle[prop] = this.originalStyle[prop];
			}

			this.element.unbind('.masonry').removeClass('masonry').removeData(
					'masonry');

			$(window).unbind('.masonry');

		}

	};

	// ======================= imagesLoaded Plugin ===============================
	/*!
	 * jQuery imagesLoaded plugin v1.1.0
	 * http://github.com/desandro/imagesloaded
	 *
	 * MIT License. by Paul Irish et al.
	 */

	// $('#my-container').imagesLoaded(myFunction)
	// or
	// $('img').imagesLoaded(myFunction)
	// execute a callback when all images have loaded.
	// needed because .load() doesn't work on cached images
	// callback function gets image collection as argument
	// `this` is the container
	$.fn.imagesLoaded = function(callback) {
		var $this = this, $images = $this.find('img').add($this.filter('img')), len = $images.length, blank = '', loaded = [];

		function triggerCallback() {
			callback.call($this, $images);
		}

		function imgLoaded(event) {
			var img = event.target;
			if (img.src !== blank && $.inArray(img, loaded) === -1) {
				loaded.push(img);
				if (--len <= 0) {
					setTimeout(triggerCallback);
					$images.unbind('.imagesLoaded', imgLoaded);
				}
			}
		}

		// if no images, trigger immediately
		if (!len) {
			triggerCallback();
		}

		$images.bind('load.imagesLoaded error.imagesLoaded', imgLoaded).each(
				function() {
					// cached images don't fire load sometimes, so we reset src.
					var src = this.src;
					// webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f
					// data uri bypasses webkit log warning (thx doug jones)
					this.src = blank;
					this.src = src;
				});

		return $this;
	};

	// helper function for logging errors
	// $.error breaks jQuery chaining
	var logError = function(message) {
		if (window.console) {
			window.console.error(message);
		}
	};

	// ======================= Plugin bridge ===============================
	// leverages data method to either create or return $.Mason constructor
	// A bit from jQuery UI
	// https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js
	// A bit from jcarousel
	// https://github.com/jsor/jcarousel/blob/master/lib/jquery.jcarousel.js

	$.fn.masonry = function(options) {
		if (typeof options === 'string') {
			// call method
			var args = Array.prototype.slice.call(arguments, 1);

			this
					.each(function() {
						var instance = $.data(this, 'masonry');
						if (!instance) {
							logError("cannot call methods on masonry prior to initialization; "
									+ "attempted to call method '"
									+ options
									+ "'");
							return;
						}
						if (!$.isFunction(instance[options])
								|| options.charAt(0) === "_") {
							logError("no such method '" + options
									+ "' for masonry instance");
							return;
						}
						// apply method
						instance[options].apply(instance, args);
					});
		} else {
			this.each(function() {
				var instance = $.data(this, 'masonry');
				if (instance) {
					// apply options & init
					instance.option(options || {});
					instance._init();
				} else {
					// initialize new instance
					$.data(this, 'masonry', new $.Mason(options, this));
				}
			});
		}
		return this;
	};

})(window, jQuery);